//******************************************************************************
//this code is protected by the GNU affero GPLv3
//author:Sylvain BERTRAND <sylvain.bertrand AT gmail dot com>
//                        <digital.ragnarok AT gmail dot com>
//******************************************************************************
#define LWL_UDEV_C
#include "globals.h"
#undef LWL_UDEV_C

#define INPUT_STR "input"
#define DCE6_DISPLAY_STR "dce6_display"

static struct udev *lwl_udev;
static struct udev_monitor *monitor;

void lwl_udev_init(i ep_fd)
{
  lwl_udev=udev_new();
  if(!lwl_udev){
    PERRC("fatal:unable to init udev context\n");
    exit(LWL_ERR);
  }

  //get events *after* udev rule engine processing
  monitor=udev_monitor_new_from_netlink(lwl_udev,"udev");
  if(!monitor){
    PERRC("fatal:unable to create an udev monitor\n");
    exit(LWL_ERR);
  }

  l r=(l)udev_monitor_filter_add_match_subsystem_devtype(monitor,INPUT_STR,0);
  if(r<0){
    PERRC("fatal:unable to add " INPUT_STR 
                                          " subsystem match to udev monitor\n");
    exit(LWL_ERR);
  }

  r=(l)udev_monitor_filter_add_match_subsystem_devtype(monitor,DCE6_DISPLAY_STR,
                                                                             0);
  if(r<0){
    PERRC("fatal:unable to add " DCE6_DISPLAY_STR
                                          " subsystem match to udev monitor\n");
    exit(LWL_ERR);
  }

  r=(l)udev_monitor_enable_receiving(monitor);
  if(r<0){
    PERRC("fatal:unable to enable receiving of udev monitor events\n");
    exit(LWL_ERR);
  }

  i monitor_fd=udev_monitor_get_fd(monitor);

  struct epoll_event ep_evt;
  memset(&ep_evt,0,sizeof(ep_evt));
  ep_evt.events=EPOLLIN|EPOLLHUP;//*level* triggered
  ep_evt.data._32=EPOLL_UDEV;

  r=epoll_ctl(ep_fd,EPOLL_CTL_ADD,monitor_fd,&ep_evt);
  if(ISERR(r)){
    PERR("fatal(%ld):unable to register udev monitor socket(%d) to epoll\n",r,
                                                                    monitor_fd);
    exit(LWL_ERR);
  }
}

static s32 seat_find(void *id_seat)
{
  s32 seat_name=0;
  while(1){
    if(seat_name==REGISTRY_N_MAX) break;
    if(registry[seat_name].itf==WL_SEAT){
      LOG_UDEV("seat_find:%s(%d)\n",&registry[seat_name].seat.id[0],seat_name);
      if(strncmp(id_seat,&registry[seat_name].seat.id[0],FREEDESKTOP_SEAT_ID_SZ)
                                                                            ==0)
        return seat_name;
    }
    ++seat_name;
  }
  return LWL_ERR;
}

#define EVENT_STR "event"
static void input_device_add(struct udev_device *d)
{
  void *devnode=(void*)udev_device_get_devnode(d);
  void *sysname=(void*)udev_device_get_sysname(d);
  LOG_UDEV("adding input device %s:%s\n",devnode,sysname);

  void *id_seat=(void*)udev_device_get_property_value(d,"ID_SEAT");
  if(!id_seat) id_seat=FREEDESKTOP_DEFAULT_SEAT_ID;

  if(strncmp(EVENT_STR,sysname,cs_n(EVENT_STR))){
    LOG_UDEV("not a input event device, skipping.\n");
    goto exit;
  }

  s32 seat_name=seat_find(id_seat);
  if(seat_name==LWL_ERR){
    seat_name=seat_new(id_seat);
    if(seat_name==LWL_ERR){
      LOG_UDEV("unable to get a proper registry seat, skipping.\n");
      goto exit;
    }
    LOG_UDEV("new seat(%s:%d) created\n",id_seat,seat_name);
  }
  registry_gbl_ref(&registry[seat_name]);

  s8 evdev=lwl_evdev_new(devnode,seat_name);
  if(evdev==LWL_ERR){
    LOG_UDEV("unable to init an internal event device, skipping.\n");
    goto err_unref_seat;
  }

  seat_input_change(seat_name);

  LOG_UDEV("input event device %s on seat=%s added\n",devnode,id_seat);

err_unref_seat:
  registry_gbl_unref(&registry[seat_name]);
exit:
  return;
}

//the following is because of the udev_list_entry_foreach macro
//which does contain the NULL preprocessor definition
#define NULL 0
//we have no seat filters, we account all input devices and all dce6 display
//devices
void lwl_udev_input_cold_plug(void)
{
  struct udev_enumerate *e;

  e=udev_enumerate_new(lwl_udev);
  if(!e){
    PERRC("fatal:cold plug:unable to create enumeration\n");
    exit(LWL_ERR);
  }

  int r=udev_enumerate_add_match_subsystem(e,"input");
  if(r){
    PERRC("fatal:cold plug:unable to select input subsystem for enumeration\n");
    exit(LWL_ERR);
  }

  r=udev_enumerate_add_match_subsystem(e,"dce6_display");
  if(r){
    PERRC("fatal:cold plug:unable to select dce6_display subsystem for"
                                                              " enumeration\n");
    exit(LWL_ERR);
  }

  r=udev_enumerate_scan_devices(e);
  if(r){
    PERRC("fatal:cold plug:error while enumerating input devices\n");
    exit(LWL_ERR);
  }

  struct udev_list_entry *first=udev_enumerate_get_list_entry(e);
  struct udev_list_entry *item=0;
  LOG_UDEV("cold plug:starting input devices enumeration...\n");
  udev_list_entry_foreach(item,first){
    struct udev_device *d=udev_device_new_from_syspath(lwl_udev,
                                                udev_list_entry_get_name(item));

    if(!d) continue;

    void *subsys=(void*)udev_device_get_subsystem(d);

    if(!strncmp(INPUT_STR,subsys,cs_n(INPUT_STR)))
      input_device_add(d);
    else if(!strncmp(DCE6_DISPLAY_STR,subsys,cs_n(DCE6_DISPLAY_STR))){
      struct gbl *o=output_plugged(d);
      if(o) registry_gbl_ref(o);
    }
    udev_device_unref(d);
  }
  LOG_UDEV("cold plug:end of input devices enumeration\n");
  udev_enumerate_unref(e);
}
#undef NULL

void lwl_udev_monitor_rd(void)
{
  struct udev_device *d;

  LOG_UDEV("monitor:starting processing of udev input event\n");

  d=udev_monitor_receive_device(monitor);
  if(!d){
    PERRC("fatal:udev:monitor:unable to get device\n");
    exit(LWL_ERR);
  }

  void *action=(void*)udev_device_get_action(d);
  if(!action) goto unref_device;

  void *sysname=(void*)udev_device_get_sysname(d);
  void *subsys=(void*)udev_device_get_subsystem(d);
  LOG_UDEV("monitor:action=%s seq=%lld subsystem=%s sysname=%s\n",action,
                                      udev_device_get_seqnum(d),subsys,sysname);

  if(!strncmp(action,"add",cs_n("add"))){
    if(!strncmp(INPUT_STR,subsys,cs_n(INPUT_STR)))
      input_device_add(d);
    else if(!strncmp(DCE6_DISPLAY_STR,subsys,cs_n(DCE6_DISPLAY_STR))){
      struct gbl *o=output_plugged(d);
      if(o) registry_gbl_ref(o);
    }
  }else if (!strncmp(action,"remove",cs_n("remove"))){
    if(!strncmp(INPUT_STR,subsys,cs_n(INPUT_STR)))
      //XXX:The evdev device may be already gone with a fd error which would
      //have killed the device. This is for udev based removal of a device.
      lwl_evdev_remove((void*)udev_device_get_devnode(d));
    else if(!strncmp(DCE6_DISPLAY_STR,subsys,cs_n(DCE6_DISPLAY_STR))){
       struct gbl *o=output_gone(d);
       if(o) registry_gbl_unref(o);
    }
  }

unref_device:
  udev_device_unref(d);
  LOG_UDEV("monitor:end of udev input event processing\n");
}
